Math Sci Life Code Log in

Codes

Code your ideas for understanding of natural systems

Updated at 2020.1.3 Updated at 2020.11.01 Updated at 2020.09.26

Intro

이 문서는 다음 포스트를 보고 작성한 것이다. Basic CRUD operations in Blazor using SQLite as the database

Blazor Server Application에서 SQLite를 활용하여 CRUD (Create, Read, Update and Delete operations)를 구현해보는 포스트로 매우 유용하며, 아래의 순서대로 진행해 보면 된다. SQLite는 MySQL나 PostgreSQL와 같은 데이터베이스 관리 시스템이지만, 서버가 아니라 응용 프로그램에 넣어 사용하는 비교적 가벼운 데이터베이스이다. 영어권에서는 '에스큐엘라이트'또는 '시퀄라이트'라고 읽는다. [위키백과]

Start New Project

Visual Studio 2019에서 Blazor Server Application을 선택하여 새로운 프로젝트를 시작한다.

Adding NuGet Packages

다음 2개의 라이브러리를 누겟 패키지에서 검색하여 설치한다.

  • Microsoft.EntityFrameworkCore.Sqlite
  • Microsoft.EntityFrameworkCore.Tools

Adding Class for Data Processing

프로젝트 내 Data 디렉토리를 만들고 Data 처리를 위한 다음 3개의 Class를 추가한다.

  • Product.cs: 원하는 형태의 데이터에 따라 구성을 변경하면 된다.
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Price { get; set; }
    public string Description { get; set; }
    public int Quantity { get; set; }
}
  • ProductDbContext.cs: DbContext 클래스를 상속 받아 커스터마이즈화 한다.
    • Constructor
    • DbSet 프로퍼티
    • OnModelCreating 메서드
    • 간단한 테스트를 위한 GetProducts 메서드
public class ProductDbContext : DbContext
{
#region Constructor
public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options)
{
}
#endregion
#region Public properties
public DbSet<Product> Product { get; set; }
#endregion
#region Overidden methods
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>().HasData(GetProducts());
    base.OnModelCreating(modelBuilder);
}
#endregion
#region Private methods
private List<Product> GetProducts()
{
    return new List<Product>
    {
        new Product { Id = 1001, Name = "Laptop", Price = 20.02, Quantity = 10, Description ="This is a best gaming laptop"},
        new Product { Id = 1002, Name = "Microsoft Office", Price = 20.99, Quantity = 50, Description ="This is a Office Application"},
        new Product { Id = 1003, Name = "Lazer Mouse", Price = 12.02, Quantity = 20, Description ="The mouse that works on all surface"},
        new Product { Id = 1004, Name = "USB Storage", Price = 5.00, Quantity = 20, Description ="To store 256GB of data"}
    };
}
#endregion
}
  • ProductServices.cs: DB에 연결된 DbContext를 가지고 CRUD 작업을 하는 실질적인 클래스로서 나중에 Startup.cs에 서비스로 등록된다. DB Access시에 지연이 발생할 수 있기 때문에 대부분의 메서드가 비동기화로 선언 및 구현된다.
    • Constructor: DbContext 초기화
    • GetProductAsync
    • AddProductAsync
    • UpdateProductAsync
    • DeleteProductAsync
public class ProductServices
{
    #region Private members
    private ProductDbContext dbContext;
    #endregion
    #region Constructor
    public ProductServices(ProductDbContext dbContext)
    {
        this.dbContext = dbContext;
    }
    #endregion
    #region Public methods
    /// <summary>
    /// This method returns the list of product
    /// </summary>
    /// <returns></returns>
    public async Task<List<Product>> GetProductAsync()
    {
        return await dbContext.Product.ToListAsync();
    }
    /// <summary>
    /// This method add a new product to the DbContext and saves it
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public async Task<Product> AddProductAsync(Product product)
    {
        try
        {
            dbContext.Product.Add(product);
            await dbContext.SaveChangesAsync();
        }
        catch (Exception)
        {
            throw;
        }
        return product;
    }
    /// <summary>
    /// This method update and existing product and saves the changes
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public async Task<Product> UpdateProductAsync(Product product)
    {
        try
        {
            var productExist = dbContext.Product.FirstOrDefault(p => p.Id == product.Id);
            if (productExist != null)
            {
                dbContext.Update(product);
                await dbContext.SaveChangesAsync();
            }
        }
        catch (Exception)
        {
            throw;
        }
        return product;
    }
    /// <summary>
    /// This method removes and existing product from the DbContext and saves it
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public async Task DeleteProductAsync(Product product)
    {
        try
        {
            dbContext.Product.Remove(product);
            await dbContext.SaveChangesAsync();
        }
        catch (Exception)
        {
            throw;
        }
    }
    #endregion
}

Modifying import razor file

_imports.razor: 프로젝트 내 모든 razor파일에서 Data 폴더의 Data 관련 Class를 바로 사용하기 위해 다음과 같은 using 문장 추가한다.

@using [Your_Namespace].Data

Modifying Startup cs file

Startup.cs: ProductDbContextProductServices를 등록한다. 여기서는 DB파일 이름은 Products.db으로 하였다. ConfigureServices 관련한 부분은 나중에 추가적인 공부가 필요하다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();

    services.AddDbContext<ProductDbContext>(options =>
    {
        options.UseSqlite("Data Source = Products.db");
    });
    services.AddScoped<ProductServices>();
}

Modifying Web Page

DB 사용을 위한 모든 기능 구현이 완료되었다. 이제 Web 사용자가 실제로 보는 User Interface (UI)을 바꿔야 하는데, 여기서는 Index.razor 파일에 직접 DB를 테이블 형태로 보여주고, 신규 추가 및 수정하는 UI를 꾸민다. 관련한 문법 및 상세한 사용법은 차차 익히도록 하자.

@page "/"
@inject ProductServices service
<div class="container">
    <div class="row bg-light">
        <table class="table table-bordered">
            <thead class="thead-dark">
                <tr>
                    <th>Product Id</th>
                    <th>Name</th>
                    <th>Price</th>
                    <th>Quantity</th>
                    <th>Description</th>
                    <th>Delete Product</th>
                </tr>
            </thead>
            <tbody>
                @if (Products.Any())
                {
                    @foreach (var product in Products)
                    {
                        <tr @onclick="(() => SetProductForUpdate(product))">
                            <td>@product.Id</td>
                            <td>@product.Name</td>
                            <td>@product.Price</td>
                            <td>@product.Quantity</td>
                            <td>@product.Description</td>
                            <td><button class="btn btn-danger" @onclick="(() => DeleteProduct(product))">Delete</button></td>
                        </tr>
                    }
                }
                else
                {
                    <tr><td colspan="6"><strong>No products available</strong></td></tr>
                }
            </tbody>
        </table>
    </div>
    <div class="row m-5">
        <div class="col-5 bg-light m-2 justify-content-start">
            <div class="p-3 mb-3 bg-primary text-white text-center">Add New Product</div>
            <EditForm Model="@NewProduct">
                <div class="form-group">
                    <label for="name">Product Name</label>
                    <input type="text" id="name" class="form-control" @bind-value="@NewProduct.Name" />
                </div>
                <div class="form-group">
                    <label for="price">Price</label>
                    <input type="text" id="price" class="form-control" @bind="@NewProduct.Price" />
                </div>
                <div class="form-group">
                    <label for="quantity">Quantity</label>
                    <input type="text" id="quantity" class="form-control" @bind="@NewProduct.Quantity" />
                </div>
                <div class="form-group">
                    <label for="Description">Description</label>
                    <input type="text" id="Description" class="form-control" @bind="@NewProduct.Description" />
                </div>
                <div class="text-center p-3 mb-3">
                    <button class="btn btn-info" @onclick="AddNewProduct"> Add Product</button>
                </div>
            </EditForm>
        </div>
        <div class="col-5 bg-light m-2 justify-content-end">
            <div class="p-3 mb-1 bg-primary text-white text-center">Update Product</div>
            <EditForm Model="@UpdateProduct">
                <div class="form-group">
                    <label for="name">Product Name</label>
                    <input type="text" id="name" class="form-control" @bind-value="@UpdateProduct.Name" />
                </div>
                <div class="form-group">
                    <label for="price">Price</label>
                    <input type="text" id="price" class="form-control" @bind="@UpdateProduct.Price" />
                </div>
                <div class="form-group">
                    <label for="quantity">Quantity</label>
                    <input type="text" id="quantity" class="form-control" @bind="@UpdateProduct.Quantity" />
                </div>
                <div class="form-group">
                    <label for="Description">Description</label>
                    <input type="text" id="Description" class="form-control" @bind="@UpdateProduct.Description" />
                </div>
                <div class="text-center p-3 mb-3">
                    <button class="btn btn-info" @onclick="UpdateProductData"> Update Product</button>
                </div>
            </EditForm>
        </div>
    </div>
</div>
@code {
    List<Product> Products = new List<Product>();
    protected override async Task OnInitializedAsync()
    {
        await RefreshProducts();
    }
    private async Task RefreshProducts()
    {
        Products = await service.GetProductAsync();
    }
    public Product NewProduct { get; set; } = new Product();
    private async Task AddNewProduct()
    {
        await service.AddProductAsync(NewProduct);
        NewProduct = new Product();
        await RefreshProducts();
    }
    Product UpdateProduct = new Product();
    private void SetProductForUpdate(Product product)
    {
        UpdateProduct = product;
    }
    private async Task UpdateProductData()
    {
        await service.UpdateProductAsync(UpdateProduct);
        await RefreshProducts();
    }
    private async Task DeleteProduct(Product product)
    {
        await service.DeleteProductAsync(product);
        await RefreshProducts();
    } 
}

Modifying site css file

site.css: 마우스가 위로 움직일 때 웹페이지의 Table의 Background 색깔을 변경할 수 있다. 꼭 필요한 것은 아니나, CSS에 대해 공부하여 추후 더 다양하 기능을 넣어보자.

tr:hover {
    background-color:lightgray;
}

Migration and Update DB

실행하기 전에 DB를 초기화하여야 한다. 하기 명령을 통하 ProductDbContext 클래스의 GetProduct 메서드에서 정의된 데이터가 자동으로 업데이트 되게 할 수 있다. Package Manager Console을 열어서 Default Project를 해당 프로젝트로 선택하고, 다음 두 개의 명령어를 입력한다.

  1. Add-Migration "Initial-Commit"
  2. Update-Database

위와 같이하면 Migrations 폴더가 생기고 그 아래에 관련 파일(.cs)들이 생긴다. SQLiteStudio를 다운 받아서 Products.db를 열면 Database가 제대로 생성되었는지 확인해 볼 수 있다.

Trying Run

Ctrl+F5를 눌러서 Web를 실행시켜 보면 제대로 되는 것을 확인할 수 있다.

Publishing to Azure

이 프로젝트를 Azure로 Publish하기만 하면 끝인데, 현재까지는 다음과 같은 에러가 발생한다. 아직 해결책을 못찾았다. 아마 Azure에서는 SQLite를 막아놓은 것 같다.

Error.
An error occurred while processing your request.
Development Mode
Swapping to Development environment will display more detailed information about the error that occurred.

The Development environment shouldn't be enabled for deployed applications. It can result in displaying sensitive information from exceptions to end users. For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development and restarting the app.

17 개의 글이 있습니다.

# 제목 날짜 조회수
01 CS 배우기 요약 2021/06/07 144
02 CS Statements 2021/06/07 128
03 퍼셉트론 2021/04/15 124
04 Blazor and Sqlite 2021/04/15 137
05 Blazor Layouts 2021/04/15 160
06 CS Language Reference 2021/06/07 126
07 VSCode and Markdown 2021/04/15 137
08 Blazor에서 이미지파일 다루기 2021/06/10 211
09 Blazor and Markdown 2021/04/15 144
10 종속성 주입 2021/06/07 152
11 Blazor에서 데이터 다루기 2021/06/07 137
12 Blazor Components 2021/04/15 147
13 CS Glossary 2021/06/07 125
14 Enum 타입 다루기 2021/12/14 134
15 생활코딩 CS01 2022/04/25 261
16 생활코딩 CS02 2022/04/30 165
17 생활코딩 CS03 2022/04/30 441

Most Popular #3

Recent #3

An error has occurred. This application may no longer respond until reloaded. Reload 🗙